#include <assert.h>
#include <cassert>
#include <iomanip>
#include <iostream>
#include <map>

#include "../headers/CPU.h"
#include "../headers/Constants.h"
#include "../headers/Shell.h"
#include "../headers/SmallWord.h"
#include "../headers/ShellSwitchRequest.h"
#include "../headers/ListShellsRequest.h"
#include "../headers/Word.h"

using namespace std;

/** \brief application entry point
 * \param argc: argument count
 * \param argv: argument values
 */
int main(int argc, char** argv)
{
	populate_pseudo_constants();

	map<string,Shell*> shells;

	Shell* currentShell = new Shell("main", argc == 2 ? *(argv + 1) : "");
	shells.insert(pair<string,Shell*>(currentShell->get_name(), currentShell));

	ShellStates lastShellState;
	while(shells.size() > 0)
	{
		// if we have no shell, get the first one in the map
		if(currentShell == NULL)
		{
			currentShell = shells.begin()->second;
		}

		try
		{
			lastShellState = currentShell->run();
		}
		catch(ListShellsRequest)
		{
			lastShellState = currentShell->get_state();

			cout << "The following shells exist:" << endl;
			for(map<string,Shell*>::const_iterator it = shells.begin(); it != shells.end(); it++)
			{
				cout << "\t" << (*it).first << endl;
			}
		}
		catch(ShellSwitchRequest request)
		{
			lastShellState = currentShell->get_state();

			map<string,Shell*>::iterator it = shells.find(request.switch_to);
			if(it != shells.end())
			{
				currentShell = (*it).second;
			}
			else
			{
				cout << "ERROR: The shell '" << request.switch_to << "' does not exist" << endl;
			}
		}
		catch(Shell* clonedShell)
		{
			lastShellState = currentShell->get_state();

			// When a shell is cloned, it should enter 'suspended' state
			assert(lastShellState == Suspended);

			// register cloned shell
			pair<map<string,Shell*>::iterator,bool> inserted = shells.insert(pair<string,Shell*>(clonedShell->get_name(), clonedShell));

			if(inserted.second == true)
			{
				// switch to the new cloned shell
				// mark the state of the current shell
				currentShell = clonedShell;
			}
			else
			{
				cout << "ERROR: A shell named '" << clonedShell->get_name() << "' already exists. Use the 'switch' command to switch to this shell and terminate this shell before creating a clone with this name." << endl;

				// get rid of the cloned shell
				delete clonedShell;
			}
		}

		switch(lastShellState)
		{
			case Suspended:
				break;

			case Terminated:
				cout << "Terminating shell '" << currentShell->get_name() << "'" << endl;
				shells.erase(currentShell->get_name());

				delete currentShell;
				currentShell = NULL;
				break;

			default:
				assert(false);
				break;
		}
	}

	return 0;
}
